//	--------------------------------------------------------------------
//	Copyright(C) 2006,2007 Zhao Yukun. All rights reserved.
//	This file is a part of the Xeres system.
//	Xeres 2007
//	Contact : xeres.engine@gmail.com
//	$(reservedInfo)
//	$(reservedDeclaration)
//	--------------------------------------------------------------------
///	@file	<cache_stack.h>
///	@path	~/src/lib/memory/
///	@date	2007/11/17
///	@desc	Cache stack, cache can restore to last state.

#pragma once

#include "xeres.h"

namespace xeres {

	/*!
		\class	cache_stack
		\brief	Cache stack, saves & restores cache states.
	*/
	template <
		typename _Allocator 
	> class cache_stack
	{
	public:

		/// \ctor
		cache_stack( size_t page_capacity , const _Allocator& allocator = _Allocator() )
			: m_pageCapacity( page_capacity )
			, m_allocator( allocator )
			, m_rootPage( NULL )
			, m_currentPage( NULL )
		{
			m_currentPage = m_rootPage = new_page( m_pageCapacity );
		}

		/// \dtor
		~cache_stack( void )
		{
			assert( m_currentPage == m_rootPage );
			destroy_pages( m_rootPage );
		}

	//@ interface

		/*!
			\brief		Allocate space from cache.
		*/
		void * allocate( size_t size )
		{
			assert( size > 0 );
			size = ALIGNOF( size , ALIGN_SIZE );

			while( m_currentPage->avail < size )
			{
				if( m_currentPage->next == NULL )
				{
					_CachePage * p = new_page( size );
					m_currentPage->next = p;
					m_currentPage = p;
				}
				else
				{
					m_currentPage = m_currentPage->next;
					m_currentPage->avail = m_currentPage->total;
					m_currentPage->space = m_currentPage->ptr;
				}
			}
			assert( size <= m_currentPage->avail );
			void * ptr = m_currentPage->space;
			m_currentPage->avail -= size;
			m_currentPage->space += size;
			memset( ptr , 0 , size );
			return ptr;
		}

	private:

		// cache page
		struct ALIGNED _CachePage
		{
			byte *		ptr;	// allocated space base
			byte *		space;	// next available space
			size_t		total;	// total memory size
			size_t		avail;	// available size
			_CachePage * next;	// next cache page
			int			pad[3]; // padding
		};

		// new page
		_CachePage * new_page( size_t size )
		{
			size = size > m_pageCapacity ? size : m_pageCapacity;
			_CachePage * np = 
				static_cast<_CachePage*>( m_allocator.allocate( sizeof(_CachePage) + size ) );
			np->total = size;
			np->avail = size;
			np->ptr = static_cast<byte*>(np) + sizeof(_CachePage);
			np->space = np->ptr;
			np->next = NULL;
			return np;
		}

		// destroy page
		void destroy_pages( _CachePage * pages )
		{
			while( pages )
			{
				_CachePage * p = pages;
				pages = p->next;
				m_allocator.deallocate( p );
			}
		}

	//@ data

		// page info
		_CachePage *		m_rootPage;
		_CachePage *		m_currentPage;

		// allocator
		size_t			m_pageCapacity;
		_Allocator		m_allocator;

	public:

		//@ visitor

		/*!
			\struct		_State
			\brief		Take a note of cache stack's _State.
		*/
		struct _State
		{
		public:

			/// \ctor
			_State( cache_stack& cs )
				: m_cacheStack( cs )
				, m_savedPage( cs.m_currentPage )
				, m_savedAvail( cs.m_currentPage->avail )
				, m_savedSpace( cs.m_currentPage->space )
			{
			}

			/// \dtor
			~_State( void )
			{
				m_cacheStack.m_currentPage = m_savedPage;
				m_savedPage->avail = m_savedAvail;
				m_savedPage->space = m_savedSpace;
			}

		private:

		//@ data
			cache_stack&	m_cacheStack;
			mutable typename cache_stack< void >::_CachePage * m_savedPage;
			size_t			m_savedAvail;
			byte *			m_savedSpace;
		};

		//@ friend
		friend struct _State;
	};

} // namespace xeres
